#pragma once
#include <chrono>
#include <map>
#include <iostream>

typedef std::chrono::time_point<std::chrono::high_resolution_clock> timepoint;

class TimePointRecord
{
public:
    TimePointRecord(timepoint t, std::string msg) : time(t), message(msg){};
    timepoint time;
    std::string message;
};

class Timer
{
public:
    static Timer &getInstance()
    {
        static Timer instance;
        return instance;
    }

    void start(std::string name = "default", std::string message = "")
    {
        auto time = std::chrono::high_resolution_clock::now();
        if (message.size() == 0)
        {
            message = format("Timer %s start", name.c_str());
        }
        TimePointRecord record(time, message);
        std::vector<TimePointRecord> records{record};
        if (recordsCache.find(name) != recordsCache.end())
        {
            logError(format("Timer %s has already started. Please Check!", name.c_str()));
            return;
        }
        recordsCache.insert(std::make_pair(name, records));
        log(record);
    }

    template <typename... Args>
    void start(std::string name = "default", std::string &formatString = "", Args... args)
    {
        auto time = std::chrono::high_resolution_clock::now();
        std::string message = format(formatString, args...);
        TimePointRecord record(time, message);
        std::vector<TimePointRecord> records{record};
        if (recordsCache.find(name) != recordsCache.end())
        {
            logError(format("Timer %s has already started. Please Check!", name.c_str()));
            return;
        }
        recordsCache.insert(std::make_pair(name, records));
        log(record);
    }

    void record(std::string name = "default", std::string message = "")
    {
        auto now = std::chrono::high_resolution_clock::now();
        auto it = recordsCache.find(name);
        if (it == recordsCache.end())
        {
            logError(format("Timer %s does not start. Please Check!", name.c_str()));
            return;
        }
        auto &records = it->second;
        int n = records.size();
        auto startTime = records[0].time;
        auto previousTime = records[n - 1].time;
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - previousTime).count();
        auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count();
        if (message.size() == 0)
        {
            message = format("Timer %s (Record #%d) | Time Use: %dms |Elapsed time: %dms", name.c_str(), n, (int)duration, (int)elapsedTime);
        }
        else
        {
            message += format(" | Time Use: %dms | Elapsed time: %dms", (int)duration, (int)elapsedTime);
        }
        TimePointRecord record(now, message);
        records.push_back(record);
        log(record);
    }

    template <typename... Args>
    void record(std::string name = "default", std::string formatString = "", Args... args)
    {
        auto now = std::chrono::high_resolution_clock::now();
        auto it = recordsCache.find(name);
        if (it == recordsCache.end())
        {
            logError(format("Timer %s does not start. Please Check!", name.c_str()));
            return;
        }
        auto &records = it->second;
        int n = records.size();
        auto startTime = records[0].time;
        auto previousTime = records[n - 1].time;
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - previousTime).count();
        auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - startTime).count();
        std::string message = format(formatString, args...);
        message = message + format(" | Time Use: %dms | Elapsed time: %dms", (int)duration, (int)elapsedTime);
        TimePointRecord record(now, message);
        records.push_back(record);
        log(record);
    }

    void stop(std::string name = "default")
    {
        auto it = recordsCache.find(name);
        if (it == recordsCache.end())
        {
            logError(format("Timer %s cannot be found. Please Check!", name.c_str()));
            return;
        }
        recordsCache.erase(it);
        log(format("Timer %s stop!", name.c_str()));
    }

    template <typename... Args>
    std::string format(const std::string &format, Args... args)
    {
        int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
        if (size_s <= 0)
        {
            throw std::runtime_error("Error during formatting.");
        }
        auto size = static_cast<size_t>(size_s);
        std::unique_ptr<char[]> buf(new char[size]);
        std::snprintf(buf.get(), size, format.c_str(), args...);
        std::string output = std::string(buf.get(), buf.get() + size - 1);
        return output;
    }

    void log(TimePointRecord record)
    {
        this->addLog(record.message);
    }
    void log(std::string message)
    {
        this->addLog(message);
    }

    void logError(std::string message)
    {
        this->addError(message);
    }

private:
    Timer(){};
    Timer(Timer &) = delete;
    void operator=(Timer const &) = delete;
    static Timer *instancePtr;
    std::map<std::string, std::vector<TimePointRecord>> recordsCache;
    void addLog(std::string message)
    {
        std::cout << "Timer Log: " << message << std::endl;
    }
    void addError(std::string message)
    {
        std::cerr << "Timer Log Error: " << message << std::endl;
    }
};
